# 86. 权限控制

# 关于界面权限的构思 - 最基础的权限结构

# 表的构思

大多的权限控制都会运用角色来控制,这边结构也是运用角色来控制

有三张主表

  1. 用户表
  2. 权限表
  3. 角色表

以下图中是三个表和三个表的对应关系

8ky1bR.png (opens new window)

用户表跟角色表是多对多关系

角色表跟权限表是多对多关系

# 表的ORM代码

class Permission(models.Model):
    """
    权限表
    """
    title = models.CharField(max_length=32, verbose_name='标题')
    url = models.CharField(max_length=32, verbose_name='权限')
    
    class Meta:
        verbose_name_plural = '权限表'
        verbose_name = '权限表'
    
    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色表
    """
    name = models.CharField(max_length=32, verbose_name='角色名称')
    permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
    
    def __str__(self):
        return self.name


class User(models.Model):
    """
    用户表
    """
    name = models.CharField(max_length=32, verbose_name='用户名')
    password = models.CharField(max_length=32, verbose_name='密码')
    roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
    
    def __str__(self):
        return self.name

# 表的admin操作

from django.contrib import admin
from rbac import models


class PermissionAdmin(admin.ModelAdmin):
    list_display = ['title', 'url']  ## 显示的字段
    list_editable = ['url']  ## 显示中的字段,可修改


admin.site.register(models.Permission, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)

# 权限的全局使用变量 - settings

#  ###### 权限相关的配置 ######
PERMISSION_SESSION_KEY = 'permissions'   ## session的名称
WHITE_URL_LIST = [   ## 权限白名单
    r'^/login/$',
    r'^/logout/$',
    r'^/reg/$',
    r'

# 用户登录时把权限写入session

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from django.conf import settings

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        
        user = models.User.objects.filter(name=username, password=pwd).first()
        
        if not user:
            err_msg = '用户名或密码错误'
            return render(request, 'login.html', {'err_msg': err_msg})
        
        # 登录成功
        # 将权限信息写入到session
        
        # 1. 查当前登录用户拥有的权限
        permission_list = user.roles.filter(permissions__url__isnull=False).values_list(
                                                                                   'permissions__url').distinct()
        # for i in permission_list:
        #     print(i)
        
        # 2. 将权限信息写入到session
        print("permission_list",permission_list)
        print("permission_list_1",list(permission_list))
        request.session[settings.PERMISSION_SESSION_KEY] = list(permission_list)
        
        
        return redirect(reverse('customer'))
    
    return render(request, 'login.html')

# 使用权限表进行web访问限制

可以通过编写django的中间件来进行针对每次访问进行限制

from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re


class PermissionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 对权限进行校验
        # 1. 当前访问的URL
        current_url = request.path_info
        
        # 白名单的判断
        for i in settings.WHITE_URL_LIST:
            if re.match(i,current_url):
                return
        
        # 2. 获取当前用户的所有权限信息
        
        permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
        # 3. 权限的校验
        print(current_url)
        
        for item in permission_list:
            url = item[0]
            if re.match("^{}$".format(url), current_url):
                return
        else:
            return HttpResponse('没有权限')

# 按权限动态生成单级菜单

因为我们是按url地址来区分权限的,那要怎么识别出某些url是菜单权限,目前的做法是修改一下权限表的相结构

# ORM相关

models文件


class Permission(models.Model):
    """
    权限表
    """
    title = models.CharField(max_length=32, verbose_name='标题')
    url = models.CharField(max_length=32, verbose_name='权限')
    
    is_menu = models.BooleanField(default=False, verbose_name='是否是菜单')
    icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
    
    class Meta:
        verbose_name_plural = '权限表'
        verbose_name = '权限表'
    
    def __str__(self):
        return self.title

admin文件

from django.contrib import admin
from rbac import models


class PermissionAdmin(admin.ModelAdmin):
    list_display = ['title', 'url', 'is_menu', 'icon']
    list_editable = ['url', 'is_menu', 'icon']


admin.site.register(models.Permission, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)

# 中间件校验

from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re


class PermissionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 对权限进行校验
        # 1. 当前访问的URL
        current_url = request.path_info
        
        # 白名单的判断
        for i in settings.WHITE_URL_LIST:
            if re.match(i,current_url):
                return
        
        # 2. 获取当前用户的所有权限信息
        
        permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
        # 3. 权限的校验
        print(current_url)
        
        for item in permission_list:
            url = item['url']
            if re.match("^{}$".format(url), current_url):
                return
        else:
            return HttpResponse('没有权限')

# 登录校验写入相关

写入session相关脚本

from django.conf import settings


def init_permission(request, user):
    # 1. 查当前登录用户拥有的权限
    permission_query = user.roles.filter(permissions__url__isnull=False).values(
        'permissions__url',
        'permissions__is_menu',
        'permissions__icon',
        'permissions__title').distinct()
    
    # 存放权限信息
    permission_list = []
    
    # 存放菜单信息
    
    menu_list = []
    
    for item in permission_query:
        permission_list.append({'url': item['permissions__url']})
        
        if item.get('permissions__is_menu'):
            menu_list.append({'url': item['permissions__url'], 'icon': item['permissions__icon'],
                              'title': item['permissions__title']})
    
    # 2. 将权限信息写入到session
    request.session[settings.PERMISSION_SESSION_KEY] = permission_list
    
    # 将菜单信息写入到session
    request.session[settings.MENU_SESSION_KEY] = menu_list

views登录校验

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from rbac.server.init_permission import init_permission  ## 脚本文件
import copy


def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        
        user = models.User.objects.filter(name=username, password=pwd).first()
        
        if not user:
            err_msg = '用户名或密码错误'
            return render(request, 'login.html', {'err_msg': err_msg})
        
        # 登录成功
        # 将权限信息写入到session
        init_permission(request,user)
        
        return redirect(reverse('customer'))
    
    return render(request, 'login.html')

# 动态生成单级菜单相关

自定义inclusion_tag文件

from django import template

register = template.Library()

from django.conf import settings
import re


@register.inclusion_tag('rbac/menu.html')
def menu(request):
    menu_list = request.session.get(settings.MENU_SESSION_KEY)
    for item in menu_list:
        url = item['url']
        if re.match('^{}$'.format(url), request.path_info):
            item['class'] = 'active'
            break
    
    return {"menu_list": menu_list}

前端文件

<div class="static-menu">

    {% for item in menu_list %}
        <a href="{{ item.url }}" class="{{ item.class }}">
            <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.title }}</a>

    {% endfor %}

</div>

引用前端文件到所需文件中

<div class="menu-body">
    {% load rbac %}
    {% menu request %}
</div>

# 按权限动态生成二级菜单

根据上面的生成动态的单级菜单来修改

# ORM相关

models文件

class Menu(models.Model):
    """
    一级菜单
    """
    title = models.CharField(max_length=32, unique=True)
    icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)
    weight = models.IntegerField(default=1)
    
    class Meta:
        verbose_name_plural = '菜单表'
        verbose_name = '菜单表'
    
    def __str__(self):
        return self.title


class Permission(models.Model):
    """
    权限表
    有关联Menu的是二级菜单
    没有关联Menu的不是二级菜单,是不可以做菜单的权限
    """
    title = models.CharField(max_length=32, verbose_name='标题')
    url = models.CharField(max_length=32, verbose_name='权限')
    menu = models.ForeignKey('Menu', null=True, blank=True)
    
    parent = models.ForeignKey('Permission', null=True, blank=True)
    
    class Meta:
        verbose_name_plural = '权限表'
        verbose_name = '权限表'
    
    def __str__(self):
        return self.title

admin文件

from django.contrib import admin
from rbac import models


class PermissionAdmin(admin.ModelAdmin):
    list_display = ['title', 'url', ]
    list_editable = ['url', ]


admin.site.register(models.Permission, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)
admin.site.register(models.Menu)

# 中间件校验

from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re


class PermissionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 对权限进行校验
        # 1. 当前访问的URL
        current_url = request.path_info
        
        # 白名单的判断
        for i in settings.WHITE_URL_LIST:
            if re.match(i,current_url):
                return
        
        # 2. 获取当前用户的所有权限信息
        
        permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
        # 3. 权限的校验
        print(current_url)
        
        for item in permission_list:
            url = item['url']
            if re.match("^{}$".format(url), current_url):
                return
        else:
            return HttpResponse('没有权限')

# 登录校验写入相关

写入session相关脚本

from django.conf import settings


def init_permission(request, user):
    # 1. 查当前登录用户拥有的权限
    permission_query = user.roles.filter(permissions__url__isnull=False).values(
        'permissions__url',
        'permissions__title',
        'permissions__menu_id',
        'permissions__menu__title',
        'permissions__menu__icon',
        'permissions__menu__weight',
    ).distinct()
    
    # 存放权限信息
    permission_list = []
    
    # 存放菜单信息
    
    menu_dict = {}
    
    for item in permission_query:
        permission_list.append({'url': item['permissions__url']})
        
        menu_id = item.get('permissions__menu_id')
        
        if not menu_id:
            continue
        
        if menu_id not in menu_dict:
            menu_dict[menu_id] = {
                'title': item['permissions__menu__title'],
                'icon': item['permissions__menu__icon'],
                'weight': item['permissions__menu__weight'],
                'children': [
                    {'title': item['permissions__title'], 'url': item['permissions__url']}
                ]
            }
        else:
            menu_dict[menu_id]['children'].append(
                {'title': item['permissions__title'], 'url': item['permissions__url']})
    
    # # 2. 将权限信息写入到session
    request.session[settings.PERMISSION_SESSION_KEY] = permission_list
    
    # 将菜单信息写入到session
    request.session[settings.MENU_SESSION_KEY] = menu_dict

views登录校验

from django.shortcuts import render, HttpResponse, redirect, reverse
from rbac import models
from rbac.server.init_permission import init_permission
import copy


def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        
        user = models.User.objects.filter(name=username, password=pwd).first()
        
        if not user:
            err_msg = '用户名或密码错误'
            return render(request, 'login.html', {'err_msg': err_msg})
        
        # 登录成功
        # 将权限信息写入到session
        init_permission(request,user)
        
        return redirect(reverse('customer'))
    
    return render(request, 'login.html')

# 动态生成单级菜单相关

自定义inclusion_tag文件

from django import template

register = template.Library()

from django.conf import settings
import re
from collections import OrderedDict   ## 有序字典,因为在Python3.6下的所有版本的字典是无序的,所以为了防止生成的菜单顺序不一致,所以使用有序字典


@register.inclusion_tag('rbac/menu.html')
def menu(request):
    menu_list = request.session.get(settings.MENU_SESSION_KEY)
    
    order_dict = OrderedDict()
    
    # for i in sorted(menu_list, key=lambda x: menu_list[x]['weight'],reverse=True):
    #     order_dict[i] = menu_list[i]
    #
    # for item in order_dict.values():
    #     item['class'] = 'hide'
    #
    #     for i in item['children']:
    #
    #         if re.match("^{}$".format(i['url']), request.path_info):
    #             i['class'] = 'active'
    #             item['class'] = ''
    
    for key in sorted(menu_list, key=lambda x: menu_list[x]['weight'], reverse=True):
        order_dict[key] = menu_list[key]
        
        item = order_dict[key]
        
        item['class'] = 'hide'
        
        for i in item['children']:
            
            if re.match("^{}$".format(i['url']), request.path_info):
                i['class'] = 'active'
                item['class'] = ''
    
    return {"menu_list": order_dict}

前端文件

<div class="multi-menu">
    {% for item in menu_list.values %}
        <div class="item">
            <div class="title"><i class="fa {{ item.icon }}"></i> {{ item.title }}</div>
            <div class="body {{ item.class }}">
                {% for child in item.children %}
                    <a href="{{ child.url }}" class="{{ child.class }}">{{ child.title }}</a>
                {% endfor %}

            </div>
        </div>
    {% endfor %}


</div>

引用前端文件到所需文件中

<div class="menu-body">
    {% load rbac %}
    {% menu request %}
</div>